// 作业1
type User = {
  id: number;
  kind: string;
};
// function makeCustomer<T extends User>(u: T): T {
// Error（TS 编译器版本：v4.4.2）
// Type '{ id: number; kind: string; }' is not assignable to type 'T'.
// '{ id: number; kind: string; }' is assignable to the constraint of type'T',
// but 'T' could be instantiated with a different subtype of constraint'User'.
//   return {
//     id: u.id,
//     kind: "customer",
//   };
// }

// 报错的原因是：因为返回的对象类型 { id: number; kind: string; }
// 不符合泛型 T 的类型。虽然 T 是 User 的一个子类型，但 TypeScript 无法确定 kind: "customer" 是否符合 T 的类型。
function makeCustomer(u: User): User {
  return {
    id: u.id,
    kind: "customer",
  };
}

// 作业2
// 本道题我们希望参数 a 和 b 的类型都是一致的，即 a 和 b 同时
// 为 number 或 string 类型。当它们的类型不一致的值，TS 类型检查
// 器能自动提示对应的错误信息。
// function f(a: string | number, b: string | number) {
//   if (typeof a === "string") {
//     return a + ":" + b; // no error but b can be number!
//   } else {
//     return a + b; // error as b can be number | string
//   }
// }

function f<T extends string | number>(a: T, b: T): string {
  if (typeof a === "string") {
    return a + ":" + b; // 当 a 是 string 时，b 也会是 string
  } else {
    return a + b; // 当 a 是 number 时，b 也会是 number
  }
}

// 作业3-如何定义一个 SetOptional 工具类型，支持把给定的 keys
// 对应的属性变成可选的？对应的使用示例如下所示
// type Foo = {
//   a: number;
//   b?: string;
//   c: boolean;
// };
// 测试用例
// type SomeOptional = SetOptional<Foo, "a" | "b">;
// type SomeOptional = {
// a?: number; // 该属性已变成可选的
// b?: string; // 保持不变
// c: boolean;
// }

type SetOptional<T, K extends keyof T> = {
  [P in keyof T]: P extends K ? T[P] | undefined : T[P];
};

// 测试
type Foo = {
  a: number;
  b?: string;
  c: boolean;
};

type SomeOptional = SetOptional<Foo, "a" | "b">;

// 结果类型 SomeOptional 应该是:
// type SomeOptional = {
//   a?: number; // 该属性已变成可选的
//   b?: string; // 保持不变
//   c: boolean;
// }

// 作业4-如何定义一个 ConditionalPick 工具类型，支持根据指定
// 的 Condition 条件来生成新的类型，对应的使用示例如下：
type ConditionalPick<V, T> = {
  [K in keyof V as V[K] extends T ? K : never]: V[K];
};

// 示例接口
interface Example {
  a: string;
  b: string | number;
  c: () => void;
  d: {};
}

// 测试用例
type StringKeysOnly = ConditionalPick<Example, string>;

// 结果类型 StringKeysOnly 应该是:
// type StringKeysOnly = {
//   a: string;
// }

// 作业5-定义一个 NativeFlat 工具类型，支持把数组类型拍平（扁平
// 化）。具体的使用示例如下所示：
type NaiveFlat<T extends any[]> = T extends [infer First, ...infer Rest]
  ? First extends any[]
    ? [...NaiveFlat<First>, ...NaiveFlat<Rest>]
    : [First, ...NaiveFlat<Rest>]
  : [];

// 测试用例：
type NaiveResult = NaiveFlat<[["a"], [["b", "c"]], ["d"]]>;

// NaiveResult 的结果应该是 "a" | "b" | "c" | "d"
